add webhooks controller

Andrew Cantino 11 anni fa
parent
commit
d0715756f0

+ 1 - 0
Gemfile

@@ -1,6 +1,7 @@
1 1
 source 'https://rubygems.org'
2 2
 
3 3
 gem 'rails'
4
+gem 'rake'
4 5
 gem 'mysql2'
5 6
 gem 'devise'
6 7
 gem 'rails_admin'

+ 39 - 0
app/controllers/webhooks_controller.rb

@@ -0,0 +1,39 @@
1
+# This controller is designed to allow your Agents to receive cross-site Webhooks (posts).  When POSTed, your Agent will
2
+# have #receive_webhook called on itself with the POST params.
3
+#
4
+# Make POSTs to the following URL:
5
+#   http://yourserver.com/users/:user_id/webhooks/:agent_id/:secret
6
+# where :user_id is your User's id, :agent_id is an Agent's id, and :secret is a token that should be
7
+# user-specifiable in your Agent.  It is highly recommended that you verify this token whenever #receive_webhook
8
+# is called.  For example, one of your Agent's options could be :secret and you could compare this value
9
+# to params[:secret] whenever #receive_webhook is called on your Agent, rejecting invalid requests.
10
+#
11
+# Your Agent's #receive_webhook method should return an Array of [json_or_string_response, status_code].  For example:
12
+#   [{status: "success"}, 200]
13
+# or
14
+#   ["not found", 404]
15
+
16
+class WebhooksController < ApplicationController
17
+  skip_before_filter :authenticate_user!
18
+
19
+  def create
20
+    user = User.find_by_id(params[:user_id])
21
+    if user
22
+      agent = user.agents.find_by_id(params[:agent_id])
23
+      if agent
24
+        response, status = agent.trigger_webhook(params.except(:action, :controller, :agent_id, :user_id))
25
+        if response.is_a?(String)
26
+          render :text => response, :status => status || 200
27
+        elsif response.is_a?(Hash)
28
+          render :json => response, :status => status || 200
29
+        else
30
+          head :ok
31
+        end
32
+      else
33
+        render :text => "agent not found", :status => :not_found
34
+      end
35
+    else
36
+      render :text => "user not found", :status => :not_found
37
+    end
38
+  end
39
+end

+ 12 - 0
app/models/agent.rb

@@ -60,6 +60,11 @@ class Agent < ActiveRecord::Base
60 60
     # Implement me in your subclass of Agent.
61 61
   end
62 62
 
63
+  def receive_webhook(params)
64
+    # Implement me in your subclass of Agent.
65
+    ["not implemented", 404]
66
+  end
67
+
63 68
   # Implement me in your subclass to decide if your Agent is working.
64 69
   def working?
65 70
     raise "Implement me in your subclass"
@@ -88,6 +93,13 @@ class Agent < ActiveRecord::Base
88 93
     message.gsub(/<([^>]+)>/) { Utils.value_at(payload, $1) || "??" }
89 94
   end
90 95
 
96
+  def trigger_webhook(params)
97
+    receive_webhook(params).tap do
98
+      self.last_webhook_at = Time.now
99
+      save!
100
+    end
101
+  end
102
+
91 103
   def set_default_schedule
92 104
     self.schedule = default_schedule unless schedule.present? || cannot_be_scheduled?
93 105
   end

+ 1 - 0
config/routes.rb

@@ -16,6 +16,7 @@ Huginn::Application.routes.draw do
16 16
   match "/worker_status" => "worker_status#show"
17 17
 
18 18
   post "/users/:user_id/update_location/:secret" => "user_location_updates#create"
19
+  post "/users/:user_id/webhooks/:agent_id/:secret" => "webhooks#create"
19 20
 
20 21
   mount RailsAdmin::Engine => '/admin', :as => 'rails_admin'
21 22
 #  match "/delayed_job" => DelayedJobWeb, :anchor => false

+ 5 - 0
db/migrate/20130509053743_add_last_webhook_at_to_agents.rb

@@ -0,0 +1,5 @@
1
+class AddLastWebhookAtToAgents < ActiveRecord::Migration
2
+  def change
3
+    add_column :agents, :last_webhook_at, :datetime
4
+  end
5
+end

+ 2 - 1
db/schema.rb

@@ -11,7 +11,7 @@
11 11
 #
12 12
 # It's strongly recommended to check this file into your version control system.
13 13
 
14
-ActiveRecord::Schema.define(:version => 20130126080736) do
14
+ActiveRecord::Schema.define(:version => 20130509053743) do
15 15
 
16 16
   create_table "agents", :force => true do |t|
17 17
     t.integer  "user_id"
@@ -26,6 +26,7 @@ ActiveRecord::Schema.define(:version => 20130126080736) do
26 26
     t.datetime "created_at",                                  :null => false
27 27
     t.datetime "updated_at",                                  :null => false
28 28
     t.text     "memory",                :limit => 2147483647
29
+    t.datetime "last_webhook_at"
29 30
   end
30 31
 
31 32
   add_index "agents", ["schedule"], :name => "index_agents_on_schedule"

+ 54 - 0
spec/controllers/webhooks_controller_spec.rb

@@ -0,0 +1,54 @@
1
+require 'spec_helper'
2
+
3
+describe WebhooksController do
4
+  class Agents::WebhookReceiverAgent < Agent
5
+    cannot_receive_events!
6
+    cannot_be_scheduled!
7
+
8
+    def receive_webhook(params)
9
+      if params.delete(:secret) == options[:secret]
10
+        memory[:webhook_values] = params
11
+        ["success", 200]
12
+      else
13
+        ["failure", 404]
14
+      end
15
+    end
16
+  end
17
+
18
+  before do
19
+    stub(Agents::WebhookReceiverAgent).valid_type?("Agents::WebhookReceiverAgent") { true }
20
+    @agent = Agents::WebhookReceiverAgent.new(:name => "something", :options => { :secret => "my_secret" })
21
+    @agent.user = users(:bob)
22
+    @agent.save!
23
+  end
24
+
25
+  it "should not require login to trigger a webhook" do
26
+    @agent.last_webhook_at.should be_nil
27
+    post :create, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"
28
+    @agent.reload.last_webhook_at.should be_within(2).of(Time.now)
29
+    response.body.should == "success"
30
+    response.should be_success
31
+  end
32
+
33
+  it "should call receive_webhook" do
34
+    post :create, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"
35
+    @agent.reload.memory[:webhook_values].should == { :key => "value", :another_key => "5" }
36
+    response.body.should == "success"
37
+    response.should be_success
38
+
39
+    post :create, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "not_my_secret", :no => "go"
40
+    @agent.reload.memory[:webhook_values].should_not == { :no => "go" }
41
+    response.body.should == "failure"
42
+    response.should be_missing
43
+  end
44
+
45
+  it "should fail on incorrect users" do
46
+    post :create, :user_id => users(:jane).to_param, :agent_id => @agent.id, :secret => "my_secret", :no => "go"
47
+    response.should be_missing
48
+  end
49
+
50
+  it "should fail on incorrect agents" do
51
+    post :create, :user_id => users(:bob).to_param, :agent_id => 454545, :secret => "my_secret", :no => "go"
52
+    response.should be_missing
53
+  end
54
+end

+ 1 - 1
spec/models/agent_spec.rb

@@ -49,7 +49,7 @@ describe Agent do
49 49
     end
50 50
   end
51 51
 
52
-  describe "with a mock source" do
52
+  describe "with an example Agent" do
53 53
     class Agents::SomethingSource < Agent
54 54
       default_schedule "2pm"
55 55